package com.mq.mqclient;

import com.mq.common.BasicReturns;
import com.mq.common.BinaryTool;
import com.mq.common.Request;
import com.mq.common.Response;
import com.mq.common.exception.MqException;
import com.mq.common.Arguments.SubScribeReturns;
import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
* 表示一个TCP连接，持有Socket对象
* 写入请求/读取回应
* 管理多个 channel*/
public class Connection {
    private Socket socket = null;
    //流对象
    private InputStream inputStream;
    private OutputStream outputStream;
    private DataInputStream dataInputStream;
    private DataOutputStream dataOutputStream;
    // 记录当前 Connection 包含的 Channel
    private ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>();
    // 执⾏消费消息回调的线程池
    private ExecutorService callbackPool = Executors.newFixedThreadPool(4);

    public Connection(String host,int port) throws IOException {
        socket = new Socket(host,port);
        inputStream = socket.getInputStream();
        outputStream = socket.getOutputStream();
        dataInputStream = new DataInputStream(inputStream);
        dataOutputStream = new DataOutputStream(outputStream);
        // 创建⼀个读响应的线程,扫描线程，负责不停的从socket中读取响应数据，将数据极爱哦给对应的 channel 处理
        Thread t = new Thread(() -> {
            try {
                while (!socket.isClosed()) {
                    Response response = readResponse();
                    dispatchResponse(response);
                }
            } catch (SocketException e) {
                // 连接断开, 忽略该异常.
                // System.out.println("[Connection] 连接断开!");
            } catch (IOException | ClassNotFoundException | MqException e) {
                System.out.println("[Connection] 连接出现异常!");
                e.printStackTrace();
            }
        });
        t.start();
    }
    //使用这个方法来处理，当前的响应是一个针对控制请求的响应，还是服务器推送的消息
    private void dispatchResponse(Response response) throws IOException, ClassNotFoundException, MqException {
        if (response.getType() == 0xc) {
            // 1. 解析到服务器返回的订阅数据，推送来的数据
            SubScribeReturns subScribeReturns = (SubScribeReturns) BinaryTool.fromBytes(response.getPayload());
            // 2. 根据 channelId 找到 channel 对象
            Channel channel = channelMap.get(subScribeReturns.getChannelId());
            if (channel == null) {
                throw new MqException("该消息对应的 channel 不存在! channelId=" + subScribeReturns.getChannelId());
            }
            // 3. 执⾏ channel对象内部的对应的回调.
            callbackPool.submit(() -> {
                try {
                    channel.getConsumer().handleDelivery(subScribeReturns.getConsumerTag(),
                            subScribeReturns.getBasicProperties(),
                            subScribeReturns.getBody());
                } catch (MqException | IOException e) {
                    e.printStackTrace();
                }
            });
        } else {
            // 1. 当前响应是针对刚才控制请求的响应
            BasicReturns baseReturns = (BasicReturns)BinaryTool.fromBytes(response.getPayload());
            // System.out.printf("[Connection] 收到响应: type=0x%x, channelId=%s,ok=%b\n", response.getType(),
            // baseReturns.getChannelId(), baseReturns.isOk());
            // 2. 找到对应 Channel
            Channel channel = channelMap.get(baseReturns.getChannelId());
            if (channel == null) {
                // 这个是⼩问题, 不要抛异常
                System.out.println("[Connection] channel 不存在! channelId=" + baseReturns.getChannelId());
                return;
            }
            // 3. 把响应放到对应的 Channel 的 map 中.
            channel.putReturns(baseReturns);
        }
    }

    public void close() {
        try {
            callbackPool.shutdown();
            channelMap = null;
            inputStream.close();
            outputStream.close(); //data流也都自动关闭了
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    // 读取响应应该在另外⼀个单独的线程中完成.
    //发送请求
    public void writeRequest(Request request) throws IOException, IOException {
        dataOutputStream.writeInt(request.getType());
        dataOutputStream.writeInt(request.getLength());
        dataOutputStream.write(request.getPayload());
        dataOutputStream.flush();
        System.out.println("[Connection] 发送请求! type=" + request.getType() + ", length=" + request.getLength());
    }
    //读取响应
    public Response readResponse() throws IOException {
        Response response = new Response();
        response.setType(dataInputStream.readInt()); //先读4个字节是type
        response.setLength(dataInputStream.readInt());//再读四个字节是length
        byte[] payload = new byte[response.getLength()];//根据Length读字节
        int n = dataInputStream.read(payload);
        if (n != response.getLength()) {
            throw new IOException("读取到的响应数据不完整!");
        }
        response.setPayload(payload);
        System.out.println("[Connection] 收到响应! type=" + response.getType() + ", length=" + response.getLength());
        return response;
    }
    //在connection 中创建一个 channel
    public Channel createChannel() throws IOException {
        // 使⽤ UUID ⽣产 channelId, 以 C- 开头
        String channelId = "C-" + UUID.randomUUID().toString(); //之前的消息id也是uuid，前缀是M
        Channel channel = new Channel(channelId, this);
        // 这⾥需要先把 channel 键值对放到 Map 中. 否则后续 createChannel 的阻塞等待就等不到结果了
        channelMap.put(channelId, channel);
        //把创建 channel 的消息也告诉服务器
        boolean ok = channel.createChannel();
        if (!ok) {
            // 服务器返回创建 channel 失败!
            // 把 channelId 删除掉即可
            channelMap.remove(channelId);
            return null;
        }
        return channel;
    }

}